package com.dnjn.happiness.webapi.iam;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONWriter;
import com.dnjn.happiness.common.utils.StringUtils;
import com.dnjn.happiness.webapi.util.JwtUtil;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Headers;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.springframework.util.StopWatch;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.time.Duration;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;

/**
 * @author amber
 * @version 1.0
 * @description TODO
 * @date 2024/6/12 15:07
 **/
@Slf4j
public class MyJipIamHandler {

    public static final int INITIAL_VALUE = 262;

    @Getter
    @AllArgsConstructor
    public enum IamPath {
        /**
         *
         */
        TOKEN("/JIP/IAM/IAM4_Sys_getToken_JWT"),
        ORG("/JIP/IAM/IAM4_Org_getOrg_JWT"),
        USER("/JIP/IAM/IAM4_User_getUser_JWT"),
        POSITION("/JIP/IAM/IAM4_Position_getPosition_JWT");

        private String path;

        public String getUrl(String domain) {
            return String.format("http://%s%s", domain, path);
        }
    }

    @Accessors(chain = true)
    @Data
    public static class IamConfig {
        private String domain = "172.16.202.34:8000";
        private String jwtKey = "WPMS-F05QWIDP";
        private String jwtSecret = "3607511f-a508-4040-9b3b-70fb6513e439";
        private String username = "WPE";
        private String password = "WPE@jinko2024";

        protected static String[] orgSnStr = "00210377,00210378,00210379,00210392".split(",");

        public static IamConfig CURRENT_test = new IamConfig()
                //.setDomain("192.168.1.6:15060")
                .setDomain("172.16.202.34:8000")
                .setJwtKey("WPMS-F05QWIDP")
                .setJwtSecret("3607511f-a508-4040-9b3b-70fb6513e439")
                .setUsername("WPE")
                .setPassword("WPE@jinko2024");


        public static IamConfig CURRENT = new IamConfig()
                .setDomain("jip.jinkosolar.com")
                .setJwtKey("WPMS-GT3X0PS2")
                .setJwtSecret("a7178bd1-5f65-4164-a2d7-c7f03f75efa4")
                .setUsername("WPE")
                .setPassword("&WwLqhsUPx3C");

    }

    static {
    }

    //
    public static void main(String[] args) throws IOException, ExecutionException, InterruptedException {
        System.out.println(8 * Runtime.getRuntime().availableProcessors());

        System.out.println(Duration.of(319859444200L, ChronoUnit.NANOS));
//        IntStream.range(1, INITIAL_VALUE).forEach(System.out::print);
//        loadData();

        JSONArray arr = getUserSinglePage(1,"00210439");
//        JSONArray arr =  getUserSinglePage(261);
        arr.forEach(System.out::println);
        log.info(" {}", arr.size());
    }

    public static void loadData() throws InterruptedException, IOException {

        ExecutorService executor = new ThreadPoolExecutor(
                4 * Runtime.getRuntime().availableProcessors(),/*64*/
                300,
                0L,
                TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(1024),
                new ThreadFactoryBuilder().setNameFormat("pms-pool-%d").build(),
                new ThreadPoolExecutor.CallerRunsPolicy()
        );

        StopWatch stopWatch = new StopWatch();
/*        stopWatch.start("org");
        JSONArray list = getOrg(1);
        System.out.println(list);
        saveJson(list, "/jinko_org_list.json");
        stopWatch.stop();

        stopWatch.start("position");
//
        JSONArray posList = getPosition(1);
        System.out.println(posList);
        saveJson(list, "/jinko_position_list.json");
//        JSONArray list = getUser(domain, token, 239);
//        System.out.println(list);
//        saveJson(list, "/jinko_user_list.json");
        stopWatch.stop();*/

        stopWatch.start("user");
        JSONArray userList = new JSONArray();
        CountDownLatch latch = new CountDownLatch(INITIAL_VALUE - 2);
        AtomicInteger count = new AtomicInteger(INITIAL_VALUE);
        IntStream.range(1, INITIAL_VALUE)
                .parallel()
                .forEach(page -> {
                    try {
                        Future<JSONArray> future = executor.submit(() -> getUserSinglePage(page,null));
                        userList.addAll(future.get());
                    } catch (InterruptedException | ExecutionException e) {
                        log.error(e.getMessage(), e);
                    } finally {
                        latch.countDown();
                        log.info("page {} remainder {} latchCount {}", page, count.getAndDecrement(), latch.getCount());
                    }
                })
        ;
        latch.await();

        stopWatch.stop();
        log.info("time cost {}  {}", Duration.ofMillis(stopWatch.getTotalTimeMillis()), stopWatch.prettyPrint());
        int size = userList.size();

        saveJson(userList.subList(0, size / 2), "/jinko_user_list.json");
        saveJson(userList.subList(size/2, size-1), "/jinko_user_list2.json");
        latch.countDown();

    }

    private static void saveJson(List list, String filename) throws IOException {
        //Files.write(Paths.get("sys_org_jinko.json"), JSON.toJSONString(list).getBytes());
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filename), "UTF-8");
        String str =JSON.toJSONString(list, JSONWriter.Feature.LargeObject,JSONWriter.Feature.WriteLongAsString);
//        String str = list.toString(JSONWriter.Feature.LargeObject, JSONWriter.Feature.WriteLongAsString);
        osw.write(str);
        osw.flush();//清空缓冲区，强制输出数据
        osw.close();//关闭输出流
    }

    public static JSONArray getUserSinglePage(Integer page,String orgSN) throws IOException {

        log.info("integer：" + page + "，" + "currentThread:" + Thread.currentThread().getName());

        Map<String, Object> body = new HashMap<>();
        /**
         内部人员-1000022716
         外部人员-363424510
         公共账号-2000013083
         */
        body.put("USER_TYPE_ID", "1000022716");
        if(StringUtils.isNotBlank( orgSN)){
            body.put("ORG_SN",orgSN);
        }


        String domain = IamConfig.CURRENT.getDomain();

        Map<String, String> headers = new HashMap<>();
        headers.put("uim-login-user-id", getUimLoginUserId());

        String url = IamPath.USER.getUrl(domain);
        String response = post(url, body, headers, Map.of("page", String.valueOf(page)));

        JSONObject json = JSON.parseObject(response);
        JSONArray pageArr = json.getJSONArray("page");
        Integer size = json.getInteger("size");
        log.info("page {}, size: {}", page, size);
        return pageArr;

    }

    public static JSONArray getUser(Integer page) throws IOException {
        Map<String, Object> body = new HashMap<>();
        /**
         内部人员-1000022716
         外部人员-363424510
         公共账号-2000013083
         */
        body.put("USER_TYPE_ID", "1000022716");


        String domain = IamConfig.CURRENT.getDomain();

        Map<String, String> headers = new HashMap<>();
        headers.put("uim-login-user-id", getUimLoginUserId());

        String url = IamPath.USER.getUrl(domain);
        String response = post(url, body, headers, Map.of("page", String.valueOf(page)));

        JSONObject json = JSON.parseObject(response);
        JSONArray pageArr = json.getJSONArray("page");
        Integer size = json.getInteger("size");
        System.out.println("size: " + size);
        if (size >= 1000) {
            JSONArray tempArr = getUser(page + 1);
            pageArr.addAll(tempArr);
        }
        return pageArr;

    }

    public static JSONArray getPosition(Integer page) throws IOException {
        String domain = IamConfig.CURRENT.getDomain();
        Map<String, Object> body = new HashMap<>();
        body.put("STATUS", "1");

        Map<String, String> headers = new HashMap<>();
        headers.put("uim-login-user-id", getUimLoginUserId());


        String url = IamPath.POSITION.getUrl(domain);
        String response = post(url, body, headers, Map.of("page", String.valueOf(page)));

        JSONObject json = JSON.parseObject(response);
        JSONArray pageArr = json.getJSONArray("page");

        Integer size = json.getInteger("size");
        if (size >= 1000) {
            JSONArray tempArr = getPosition(page + 1);
            pageArr.addAll(tempArr);
        }
        return pageArr;
    }

    public static JSONArray getOrg(Integer page) throws IOException {
        String domain = IamConfig.CURRENT.getDomain();

        Map<String, Object> body = new HashMap<>();
        String updateTime = LocalDate.now().minusDays(10).format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
//        body.put("UPDATE_TIME", updateTime);

        Map<String, String> headers = new HashMap<>();
        headers.put("uim-login-user-id", getUimLoginUserId());

        String url = IamPath.ORG.getUrl(domain);
        String response = post(url, body, headers, Map.of("page", String.valueOf(page)));

        JSONObject json = JSON.parseObject(response);

        Integer size = json.getInteger("size");

        JSONArray pageArr = new JSONArray();
        if (size > 0) {
            pageArr.addAll(json.getJSONArray("page"));
            //do has next page
            JSONArray tempArr = getOrg(page + 1);
            pageArr.addAll(tempArr);
        }

        return pageArr;

    }

    private static String getUimLoginUserId() throws IOException {
        String domain = IamConfig.CURRENT.getDomain();
        String cacheKey = String.format("%s", domain);
        String userId = USER_ID_CACHE.getIfPresent(cacheKey);
        if (StringUtils.isNotBlank(userId)) {
            return userId;
        }
        // reload data
        String username = IamConfig.CURRENT.getUsername(),
                password = IamConfig.CURRENT.getPassword();
        String url = IamPath.TOKEN.getUrl(domain);
        Map<String, Object> body = new HashMap<>();

        body.put("username", username);
        body.put("password", password);

        Map<String, String> headers = new HashMap<>();
        String resp = post(url, body, headers, null);
        JSONObject json = JSON.parseObject(resp);
        String id = json.getString("msg");
        USER_ID_CACHE.put(cacheKey, id);
        return id;
    }

    public static String post(String url, Map<String, Object> bodyMap, Map<String, String> headerMap, Map<String, String> params) throws IOException {

        String token = getJwtToken();

        // reset header
        if (Objects.isNull(headerMap)) {
            headerMap = new HashMap<>();
        }
        headerMap.put("Authorization", "Bearer " + token);
        headerMap.put("Content-Type", "application/json");


        // url
        HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
        Optional.ofNullable(params).ifPresent(map -> map.forEach(urlBuilder::addQueryParameter));
        HttpUrl httpUrl = urlBuilder.build();

        // log
        StringBuilder headerStr = new StringBuilder();
        headerMap.forEach((k, v) -> {
            headerStr.append(k).append(": ").append(v).append("\n");
        });
        log.info(String.format("\n### \nPOST %s\n%s\n%s\n", httpUrl, headerStr, JSON.toJSONString(bodyMap)));
        // =================================================
        MediaType mediaType = MediaType.parse("application/json");
        RequestBody body = RequestBody.create(mediaType, JSON.toJSONString(bodyMap));
        // req
        Request request = new Request.Builder()
                .url(httpUrl)
                //.method("POST", body)
                .headers(Headers.of(headerMap))
                .post(body)
                .build();


        Response response = OkHttpClientHolder.getInstance().newCall(request).execute();
        return response.body().string();
    }

    private static String getJwtToken() {
        String domain = IamConfig.CURRENT.getDomain(), key = IamConfig.CURRENT.getJwtKey(), secret = IamConfig.CURRENT.getJwtSecret();

        String cacheKey = String.format("%s_%s", key, secret);
        String token = TOKEN_CACHE.getIfPresent(cacheKey);
        if (StringUtils.isNotBlank(token)) {
            return token;
        }
        token = JwtUtil.createJwtToken(key, secret);

        TOKEN_CACHE.put(cacheKey, token);
        return token;
    }


    public static class OkHttpClientHolder {

        private static OkHttpClient client;

        public static OkHttpClient getInstance() {
            if (Objects.isNull(client)) {
                client = new OkHttpClient().newBuilder()
                        .readTimeout(Duration.ofSeconds(30))
                        .connectTimeout(Duration.ofSeconds(30))
                        .writeTimeout(Duration.ofSeconds(15))
                        .build();
            }
            return client;
        }
    }

    static LoadingCache<String, String> TOKEN_CACHE = CacheBuilder.newBuilder()
            //设置并发级别为8，并发级别是指可以同时写缓存的线程数
            .concurrencyLevel(8)
            //设置写缓存后25min过期
            .expireAfterWrite(5, TimeUnit.MINUTES)
            //设置缓存容器的初始容量为10
            .initialCapacity(2)
            //设置缓存最大容量为100，超过100之后就会按照LRU最近虽少使用算法来移除缓存项
            .maximumSize(10)
            //设置要统计缓存的命中率
            .recordStats()
            //设置缓存的移除通知
            .removalListener(new RemovalListener<Object, Object>() {
                @Override
                public void onRemoval(RemovalNotification<Object, Object> notification) {
                    System.out.println(notification.getKey() + " was removed, cause is " + notification.getCause());
                }
            })
            .build(new CacheLoader<String, String>() {
                @Override
                public String load(String key) throws Exception {
                    String[] arr = key.split("_");
                    String key1 = arr[0], secret = arr[1];
                    return JwtUtil.createJwtToken(key1, secret);
                }
            });
    static LoadingCache<String, String> USER_ID_CACHE = CacheBuilder.newBuilder()
            //设置并发级别为8，并发级别是指可以同时写缓存的线程数
            .concurrencyLevel(8)
            //设置写缓存后25min过期
            .expireAfterWrite(25, TimeUnit.MINUTES)
            //设置缓存容器的初始容量为10
            .initialCapacity(2)
            //设置缓存最大容量为100，超过100之后就会按照LRU最近虽少使用算法来移除缓存项
            .maximumSize(10)
            //设置要统计缓存的命中率
            .recordStats()
            //设置缓存的移除通知
            .removalListener(new RemovalListener<Object, Object>() {
                @Override
                public void onRemoval(RemovalNotification<Object, Object> notification) {
                    System.out.println(notification.getKey() + " was removed, cause is " + notification.getCause());
                }
            })
            .build(new CacheLoader<String, String>() {
                @Override
                public String load(String key) throws Exception {
                    String[] arr = key.split("_");
                    return getUimLoginUserId();
                }
            });
}

